{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 向量模型的选择\n",
    "\n",
    "向量模型（embedding model）不仅在数据处理阶段将文本块向量化，还在检索阶段将用户的问题向量化并通过搜索算法寻找该问题与向量数据库中前k个语义最接近的文本块，所以向量模型是否能有效的提取文本块中的语义信息直接影响着RAG应用的效果。但我们在开发RAG应用时不能只考虑向量模型有效地提取语义信息，还要结合项目本身的算力支撑、主体语言、知识库数据安全性等其他方面综合考量。\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 一、MTEB数据集及排行榜\n",
    "[大规模文本嵌入基准](https://github.com/embeddings-benchmark/mteb)（Massive Text Embedding Benchmark，MTEB）是衡量向量模型的评估指标合集，由Hugging Face及cohere.ai共同开发，该数据集有文本语义相似度（Semantic Textual Similarity）、文本分类（Text Classification）、文本聚类（Text Clustering）、文本对分类（Text Pair Classification）、文本重排（Text Reranking）、文本检索（Text Retrieval）、双文本挖掘（Bitext Mining）以及文本摘要（Text Summarization）8个任务，包含58个数据集以及涉及112种语言，是目前为止最全面最综合的数据集。其中涉及中文的被称为C-MTEB，共有6个任务（上述8个任务中前6个）涉及35个数据集。[MTEB 排行榜](https://huggingface.co/spaces/mteb/leaderboard)中记录了近年来绝大部分向量模型在该数据集上的得分及各项指标，我们可以从该排行榜上找到适合自己的模型。\n",
    "![MTEB Chinese leaderboard](./figures/MTEB_Chinese_leaderboard.png)\n",
    "最上边第一排依次是Search Bar（搜索模型）、Model types（根据模型开源或闭源显示模型）、Model sizes（根据参数范围显示模型），排行榜第一栏中的 Overall 显示向量模型在每个任务上的得分及所有任务中的均分，Overall之后的8项分别对应着向量模型在8个任务中的得分，第二栏则对应在该任务下的语言类型，再往下就是各个向量模型的指标与在各个任务上获得的成绩。我们可以根据向量模型的指标与成绩选择适合自己项目条件的向量模型：\n",
    "\n",
    "* Model（模型名称）：点击可以进入模型详情页，我们可以参考详情页去了解更多信息及使用。\n",
    "* Model Size（模型尺寸）：指模型的参数量，单位为Million（百万），我们通常说的7B、20B的单位为Billion（十亿）这两个单位有一千倍的差距，参数量越大，模型推理的时间越长。\n",
    "* Memory Usage（内存使用）：以fp32精度运行时占用的内存，一般向量模型占用内存不会很多。\n",
    "* Embedding Dimensions（向量维数）：将text转为向量的维度，知识库数据越多向量维度越大，会加重检索的计算量导致检索的时间越长，但短的维度有可能会提取不到完整的语义，因此我们要选择恰当向量维度的向量模型。\n",
    "* Max Tokens（最大令牌数）：token在上节中提到过在此不做赘述，进行文本分块时要额外注意此指标。\n",
    "之后的则是各个任务平均成绩及单任务成绩，便于我们了解模型性能并选取适合自己项目的模型。\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 二、向量模型类型\n",
    "如今市面上的向量模型主要分为两类，一类是基于BERT（Bidirectional Encoder Representations from Transformers，基于Transformer的双向编码器表示）的向量模型，另一类则是基于LLM（Large language model，大语言模型）的向量模型。\n",
    "### 1.基于BERT的向量模型\n",
    "\n",
    "<div align=\"center\">\n",
    "  <img src=\"./figures/BERT.png\" alt=\"BERT\" width=\"800\" />\n",
    "</div>\n",
    "\n",
    "BERT是谷歌于2018年提出的语言模型，该模型在大量的文本对数据上进行预训练，同时使用双向编码方式，来更好地捕捉序列中的上下文语义关系。因此BERT在当时刷新了包括单句分类、句对分类、问答等十一个自然语言处理任务的榜单。然而BERT的输入是句对的形式，每推理一次只能获取两个句子之间的匹配程度，并不适合大规模的语义搜索任务。达姆施塔特理工大学的泛在知识处理实验室发现使用BERT在一万个句子中寻找最相似的一对句子需要进行约五千万次（n*(n-1)/2）时长大概65小时的推理。为此该实验室提出了SBERT（Sentence-BERT），SBERT在预训练时使用同一个BERT模型先后对sentence A和sentence B进行embedding，再计算损失来优化模型参数。因为SBERT在预训练时就是以单句作为输入的，所以对于同样的任务SBERT只需要进行一万次推理再逐个计算相似度就可以完成，这一过程只需要5秒。直到现在BGE、E5等基于BERT架构以及SBERT以单句作为输入的模型依然具有强悍的性能并占据着主流位置。\n",
    "\n",
    "<div align=\"center\">\n",
    "  <img src=\"./figures/SBERT.png\" alt=\"BERT\" width=\"800\" />\n",
    "</div>\n",
    "\n",
    "### 2.基于LLM的向量模型\n",
    "\n",
    "2024年初微软提出E5-mistral-7b-instruct，该模型首次将decoder-only LLM作为向量模型的网络主干。作者认为LLM已经在大量的数据上进行了预训练，不用像类BERT模型一样在文本对上进行对比预训练。因此作者仅对Mistral-7B进行了微调，当Mistral-7B只在合成数据（25%来自GPT-3.5，75%来自GPT-4）上微调时就有了不错的表现，而Mistral-7B在使用合成数据与标记数据进行微调时则远远超过了以前的方法（+2%）。而LLM与类BERT模型作为向量模型相比LLM具有超长的上下文窗口可以捕捉范围更大的语义信息，对于RAG应用来讲也可以减少RAG应用对文档分块策略的依赖，提高RAG应用的回答质量,不过LLM参数量大在实际应用中LLM对计算资源的要求也同样远超类BERT模型。时至今日MTEB排行榜也不乏以LLM作为模型主干的向量模型身影。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 三、开源/闭源模型对比\n",
    "以下是国内外具有代表性的向量模型及主要参数，我将以个人开发者的角度去分析各个向量模型并选择适合自己的向量模型。\n",
    "\n",
    "| 厂商 | 模型 | 每元token数（万） | [MTEB](https://github.com/embeddings-benchmark/mteb)得分 | 最大输入token | 向量维度 |参数数量(十亿，Billion)|是否开源|\n",
    "| --- | --- | --- | --- | --- | --- | --- | --- |\n",
    "|OpenAI|text-embedding-3-large|769.23/$|64.6|8191|3072| - |否|\n",
    "|OpenAI|text-embedding-3-small|5000/$|62.3|8191|1536| - |否|\n",
    "|阿里巴巴|text-embedding-v2| 142.86/¥ | 62.17(CMTEB) | 2028 | 1536 | - |否|\n",
    "|阿里巴巴|gte-Qwen2-7B-instruct|-|72.05(CMTEB)|131072|3584|7.6|是|\n",
    "|阿里巴巴|gte-large-zh|-|66.72(CMTEB)|512|1024|0.3|是|\n",
    "|北京智源人工智能研究院|bge-large-zh-v1.5|-|64.53(CMTEB)|512|1024|0.3|是|\n",
    "\n",
    "\n",
    "### 本地算力\n",
    "如果本地算力充足可以选择类BERT模型使用，这些模型完全可以满足RAG系统中检索的要求；反之则可以选择闭源模型的API，这些API通常由大型科技公司提供，如上表所示OpenAI及阿里巴巴都提供API服务，这些API利用云端的强大计算资源，可以在本地算力有限的情况下提供高性能的自然语言处理能力。\n",
    "### 存储空间\n",
    "知识库中的数据与向量模型的维度决定着向量数据库占用的存储空间。虽然越高的维度理论上会存储更加丰富的语义信息但同时也增加了对存储空间的需求，因此选择何种向量模型应结合项目后续知识库是否会扩充及存储空间是否冗余等信息进行综合判断。\n",
    "### 知识类型\n",
    "绝大部分项目的知识都属于某一个或几个垂类领域，因此通用数据集MTEB的成绩仅供开发者参考，开发者可以将符合前两点要求的向量模型配合多种分块策略做测试，来选出最适合项目的向量模型。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 四、使用开源模型\n",
    "我们可以通过实例化`langchain_community.embeddings.huggingface.HuggingFaceEmbeddings`来便捷地下载并在本地使用Hugging Face中的开源模型。与第一部分我们调用的Embedding API相比不用担心token成本以及每秒或每分钟查询次数等问题。\n",
    "> 其中model_name参数为Hugging Face中模型的名称，可在模型页面左上角点击复制，如下图所示：  \n",
    "\n",
    "\n",
    "<div align=\"center\">\n",
    "  <img src=\"./figures/model_name.jpg\" alt=\"BERT\" width=\"800\" />\n",
    "</div>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/Users/lta/anaconda3/envs/llm_universe_2.x/lib/python3.10/site-packages/huggingface_hub/file_download.py:1132: FutureWarning: `resume_download` is deprecated and will be removed in version 1.0.0. Downloads always resume when possible. If you want to force a new download, use `force_download=True`.\n",
      "  warnings.warn(\n"
     ]
    }
   ],
   "source": [
    "from langchain_community.embeddings.huggingface import HuggingFaceEmbeddings\n",
    "\n",
    "embedding = HuggingFaceEmbeddings(model_name='BAAI/bge-small-zh-v1.5')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "向量化后的文本长度为:512\n",
      "向量化后的文本(前五)为:[-0.045968491584062576, -0.031193967908620834, -0.0012692523887380958, 0.0776120200753212, -0.06814876943826675]\n"
     ]
    }
   ],
   "source": [
    "text_vec = embedding.embed_query('原来大模型应用开发是如此简单！')\n",
    "print(f'向量化后的文本长度为:{len(text_vec)}')\n",
    "print(f'向量化后的文本(前五)为:{text_vec[:5]}')"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
